#pragma once 
#include "index.hpp"
#include <algorithm>
#include <jsoncpp/json/json.h>
#include "Log.hpp"

namespace ns_searcher 
{
    const std::string stop_path = "cppjieba/dict/stop_words.utf8";
    class Searcher 
    {
        public:
            Searcher(){}
            ~Searcher(){}

            void Init(const std::string& input)
            {
                /*
                    由于创建索引需要把文件内容读取到内存当中，消耗还是挺大的，所以为了节省空间，
                    我们把Index设置为单例模式，这样以后每一次有Index就直接获取一次就可以了.
                */
                //1. 获取或者创建索引  
                _index = ns_index::Index::GetInstance();
                //2. 创建正排/倒排索引
                _index->BuildIndex(input);
            }
            void InitStop()
            {
                std::ifstream in(stop_path,std::ios::in);
                if (!in.is_open()){
                    std::cerr << "暂停词文件不存在!!" << std::endl;
                    return ;
                }
                std::string line;
                while(getline(in,line))
                {
                    _stop_words.insert(std::make_pair(std::move(line), true));
                }
                LOG(DEBUG,"已经构建完了暂停词");
            }
            void Search(const std::string& query, std::string* jsonstring)
            {
                //1. 对query进行分词
                std::vector<std::string> words;
                ns_util::JiebaUtil::CutString(query,&words);
                //2. [触发] 通过关键词，找到倒排拉链，并统计每一个文档FD的总权值总和，可能会有多个关键字指向相同的文档FD
                typedef struct node 
                {
                    uint64_t doc_id;
                    // std::vector<std::string> words;
                    std::string word;
                    int sum_weight;
                }node;
                std::vector<node> InvertedArray; //每一个关键字对应的所有文档ID集合
                std::unordered_map<uint64_t,int> DocIdIndex; //文档ID对应的数组下标

                //
                for (std::string word : words)
                {
                    boost::to_lower(word); //这里我们需要全部处理为小写的，所以传值传参
                    if (_stop_words.find(word) != _stop_words.end()){
                        //跳过暂停词
                        continue;
                    }
                    ns_index::InvertedList* invertedlist = _index->GetInvertedList(word);
                    if (invertedlist == nullptr){
                        std::cerr << "关键字没有为空" << std::endl;
                        continue;
                    }
                    for (auto& x : (*invertedlist))
                    {
                        if (DocIdIndex.find(x.doc_id) == DocIdIndex.end()){
                            node _node = {x.doc_id,x.word,x.weight};
                            // _node.doc_id = x.doc_id;
                            // _node.words.push_back(word);
                            // _node.sum_weight = x.weight;
                            DocIdIndex[x.doc_id] = InvertedArray.size(); //保留文档ID对应的下标位置
                            InvertedArray.push_back(std::move(_node));
                        }
                        else{
                            InvertedArray[DocIdIndex[x.doc_id]].sum_weight += x.weight; //把权值添加进去
                            // InvertedArray[DocIdIndex[x.doc_id]].words.push_back(x.word); //把关键字添加进去
                        }
                    }
                }
                //3. [合并排序]: 按照权值排序
                sort(InvertedArray.begin(),InvertedArray.end(),[&](const node& x,const node& y){
                    return x.sum_weight > y.sum_weight;
                });
                //4. [构建应答]: 构建json应答返回
                Json::Value root;
                // Json::StyledWriter w;
                Json::FastWriter w;
                for (auto& doc_node : InvertedArray)
                {
                    ns_index::DocInfor* doc = _index->GetForwardIndex(doc_node.doc_id);
                    Json::Value data;
                    data["title"] = doc->title;
                    // data["desc"] = GetDesc(doc->content,doc_node.words[0]);
                    //这里虽然可以保留多个关键字，但是摘要的话只是其中一个，所以为了节省空间，并不保存多个关键字
                    data["desc"] = GetDesc(doc->content,doc_node.word);
                    data["url"] = doc->url;
                    //for debug
                    // data["weight"] = doc_node.sum_weight;
                    // data["id"] = doc_node.doc_id;
                    // data["word"] = doc_node.word;
                    root.append(std::move(data));
                }
                *jsonstring = w.write(root);
            }
        private:
            //注意这里的参数我们要传值传递，因为我们在获取关键字的位置的时候，需要进行大小写转换。
            //关键字是被我们自己变为小写的了，但是文档内容并没有别我们设置为小写
            std::string GetDesc(std::string& content,const std::string& word)
            {
                //虽然这样也可以，但是消耗太大
                // boost::to_lower(content);
                const std::size_t prev_step = 50;
                const std::size_t next_step = 100;
                //search : c++中不区分大小写的搜索指定迭代区间的函数，返回一个迭代器(content)。
                auto iter = std::search(content.begin(),content.end(),word.begin(),word.end());
                //distance求指定迭代器的位置
                std::size_t pos = std::distance(content.begin(),iter);

                if(pos == std::string::npos) return "NONE";
                std::size_t start = 0;
                std::size_t end = content.size()-1;
                //取关键字的位置[往前50个字,往后100个字] 总共150个字节
                //注意!!!: 可能关键字前面并没有50个字，所以这个判断是 > 0(❎❎❎)，如果是负数，我们是size_t类型会转化为很大的数字;
                //所以只需要判断pos和prev_step的大小即可!!
                if (pos > prev_step) start = pos-prev_step; //如果比start大，说明关键字前50个字节的位置在start后面
                if ((pos + next_step) < end) end = (pos + next_step); //如果比end小，说明关键字后100个字节的位置在end前面
                //for debug
                // std::cout << "start: " << start << std::endl;
                // std::cout << "end: " << end << std::endl;
                // std::cout << "pos: " << pos << std::endl;
                return content.substr(start,end-start+1) + "...";
            }
            ns_index::Index* _index;
            std::unordered_map<std::string,bool> _stop_words;
    };
};
